package org.budo.phone.freeswitch;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.budo.phone.api.AbstractPhoneEventFilter;
import org.budo.phone.api.BudoPhoneEvent.Type;
import org.budo.phone.api.BudoPhoneEventListener;
import org.budo.phone.freeswitch.util.CallIdUtil;
import org.budo.phone.freeswitch.util.UserXmlUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.mvcs.Mvcs;
import org.budo.support.servlet.util.ResponseUtil;
import org.freeswitch.esl.client.IEslEventListener;
import org.freeswitch.esl.client.transport.event.EslEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

import com.alibaba.fastjson.JSON;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * @author limingwei
 */
@Slf4j
@Getter
@Setter
public class FreeswitchPhoneEventFilter extends AbstractPhoneEventFilter implements InitializingBean, DisposableBean, IEslEventListener {
    private static final Logger eslLog = LoggerFactory.getLogger("FreeswitchPhoneEventFilter.EslEvent");

    private List<FreeswitchEslClient> freeswitchEslClients = new ArrayList<FreeswitchEslClient>();

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        this.assertEslClientInited();

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        String requestURI = request.getRequestURI();

        // dialplan 请求
        if (StringUtil.endWithIgnoreCase(requestURI, "dialplan")) {
            this.handleDialplan(request, response);
            return;
        }

        // 用户认证
        if (StringUtil.endWithIgnoreCase(requestURI, "curl")) {
            this.handleCurl(request, response);
            return;
        }

        // 未使用
        if (StringUtil.endWithIgnoreCase(requestURI, "directory.lua")) {
            this.handleDirectoryLua(request, response);
            return;
        }

        // 呼入
        if (StringUtil.endWithIgnoreCase(requestURI, "call_in")) {
            this.handleCallIn(request, response);
            return;
        }

        // 签入
        if (StringUtil.contains(requestURI, "ivr/agentonwork")) {
            this.handleAgentOnWork(request, response);
            return;
        }

        // 签出
        if (StringUtil.contains(requestURI, "ivr/agentoffwork")) {
            this.handleAgentOffWork(request, response);
            return;
        }

        // 签出
        if (StringUtil.contains(requestURI, "ivr/agentoffwork")) {
            this.handleAgentOffWork(request, response);
            return;
        }

        // 示忙示闲
        if (StringUtil.contains(requestURI, "ivr/agentready")) {
            this.handleAgentReady(request, response);
            return;
        }

        // 其他请求
        super.doFilter(request, response, chain);
    }

    /**
     * 呼入回调
     */
    public void handleCallIn(HttpServletRequest request, HttpServletResponse response) {
        throw new RuntimeException();
    }

    /**
     * 签入 https://doc.yuntongxun.com/p/5a534cde3b8496dd00dce22d
     */
    public void handleAgentOnWork(HttpServletRequest request, HttpServletResponse response) {
        throw new RuntimeException();
    }

    /**
     * 迁出
     */
    public void handleAgentOffWork(HttpServletRequest request, HttpServletResponse response) {
        throw new RuntimeException();
    }

    /**
     * 示忙示闲
     */
    public void handleAgentReady(HttpServletRequest request, HttpServletResponse response) {
        throw new RuntimeException();
    }

    /**
     * handleCurl
     */
    public void handleCurl(HttpServletRequest request, HttpServletResponse response) {
        log.debug("#79 handleCurl, request=" + request + ", parameterMap=" + JSON.toJSONString(Mvcs.getRequestParameterMap(request)) + ", response=" + response);

        String section = request.getParameter("section");

        if ("directory".equals(section)) {
            this.handleCurlDirectory(request, response);
            return;
        }

        log.warn("#83 handleCurl, section=" + section + ", request=" + request + ", parameterMap=" + JSON.toJSONString(Mvcs.getRequestParameterMap(request)) + ", response=" + response);
    }

    /**
     * 默认实现，替换
     * 
     * @see DiaplanController#callAuth(HttpServletRequest)
     */
    public void handleDialplan(HttpServletRequest request, HttpServletResponse response) {
        log.info("#133 handleDialplan, request=" + request + ", parameterMap=" + JSON.toJSONString(Mvcs.getRequestParameterMap(request)) + ", response=" + response);

        // 返回拨号计划
        String caller = "1002";
        String gateway = "gw132";
        String destination_number = request.getParameter("destination_number");
        String params = "{absolute_codec_string=PCMA,origination_caller_id_number=" + caller + "}";
        String diaplan = params + "sofia/gateway/" + gateway + "/" + destination_number;
        ResponseUtil.writeToResponse(response, diaplan);
    }

    /**
     * 默认密码 1234
     */
    public void handleCurlDirectory(HttpServletRequest request, HttpServletResponse response) {
        String domain = request.getParameter("domain");
        String user = request.getParameter("user");
        String password = "1234";

        String xml = UserXmlUtil.userXml(domain, user, password);
        ResponseUtil.writeToResponse(response, xml);
    }

    /**
     * 启动 esl Client
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        this.assertEslClientInited();
    }

    /**
     * @see DisposableBean
     */
    @Override
    public void destroy() {
        super.destroy();

        for (FreeswitchEslClient client : this.getFreeswitchEslClients()) {
            client.close();
        }
    }

    /**
     * 启动时尝试连接一次，以后每次请求进来都会检查是否已经连接
     */
    protected void assertEslClientInited() {
        for (FreeswitchEslClient client : this.getFreeswitchEslClients()) {
            client.assertInited();
        }
    }

    /**
     * ESL事件回调
     */
    @Override
    public void eventReceived(EslEvent eslEvent) {
        Type eventType = this.eventType(eslEvent);
        Map<String, String> eventHeaders = eslEvent.getEventHeaders();

        if (null == eventType) {
            eslLog.warn("#189 eventName=" + eslEvent.getEventName() + ", event=" + eslEvent + ", eventHeaders=" + JSON.toJSONString(eventHeaders));
            return;
        }

        String variable_uuid = eventHeaders.get("variable_uuid");
        String caller_id_number = eventHeaders.get("Caller-Username");
        String variable_effective_caller_id_number = eventHeaders.get("variable_effective_caller_id_number");

        // 一通电话会收到两个接通，两个挂断时间；其中一个 variable_effective_caller_id_number 为空，且
        // variable_uuid 与 call_auth 时的值不同，抛弃掉
        if (null == variable_effective_caller_id_number //
                || variable_effective_caller_id_number.trim().isEmpty() //
                || variable_effective_caller_id_number.trim().equals("null")) {
            log.info("#204 skip this eslEvent, eventHeaders=" + eventHeaders);
            return;
        }

        FreeswitchPhoneEvent phoneEvent = new FreeswitchPhoneEvent();
        phoneEvent.setEventName(eslEvent.getEventName());
        phoneEvent.setType(eventType);

        phoneEvent.setCallId(CallIdUtil.uuidToCallId(variable_uuid));

        phoneEvent.setCaller(caller_id_number);

        // Caller-Destination-Number
        // variable_sip_to_user
        // variable_sip_req_user
        String destination = eventHeaders.get("Caller-Destination-Number");
        phoneEvent.setDestination(destination);

        phoneEvent.setHeaders(eventHeaders);

        BudoPhoneEventListener budoPhoneEventListener = this.getBudoPhoneEventListener();

        log.info("#213 budoPhoneEventListener=" + budoPhoneEventListener + ", phoneEvent=" + phoneEvent);
        budoPhoneEventListener.onEvent(phoneEvent);
    }

    private Type eventType(EslEvent event) {
        String eventName = event.getEventName();
        if ("CHANNEL_ANSWER".equalsIgnoreCase(eventName)) {
            return Type.CallEstablish;
        }

        if ("CHANNEL_DESTROY".equalsIgnoreCase(eventName)) {
            return Type.Hangup;
        }

        if ("HEARTBEAT".equalsIgnoreCase(eventName) //
                || "CUSTOM".equalsIgnoreCase(eventName) //
                || "MESSAGE_QUERY".equalsIgnoreCase(eventName) //
                || "CHANNEL_STATE".equalsIgnoreCase(eventName) //
                || "MESSAGE_WAITING".equalsIgnoreCase(eventName) //
                || "PRESENCE_IN".equalsIgnoreCase(eventName) //
                || "RECORD_STOP".equalsIgnoreCase(eventName) //
                || "MEDIA_BUG_STOP".equalsIgnoreCase(eventName) //
                || "CHANNEL_CALLSTATE".equalsIgnoreCase(eventName) //
                || "CHANNEL_HANGUP_COMPLETE".equalsIgnoreCase(eventName) //
                || "RECV_RTCP_MESSAGE".equalsIgnoreCase(eventName) //
                || "CHANNEL_BRIDGE".equalsIgnoreCase(eventName) //
                || "CHANNEL_PROGRESS_MEDIA".equalsIgnoreCase(eventName) //
                || "CODEC".equalsIgnoreCase(eventName) //
                || "CHANNEL_ORIGINATE".equalsIgnoreCase(eventName) //
                || "CHANNEL_CREATE".equalsIgnoreCase(eventName) //
                || "CHANNEL_EXECUTE_COMPLETE".equalsIgnoreCase(eventName) //
                || "API".equalsIgnoreCase(eventName) //
                || "CHANNEL_EXECUTE".equalsIgnoreCase(eventName) //
                || "CHANNEL_OUTGOING".equalsIgnoreCase(eventName) //
                || "CHANNEL_HANGUP".equalsIgnoreCase(eventName) //
                || "CHANNEL_UNBRIDGE".equalsIgnoreCase(eventName) //
                || "RECORD_START".equalsIgnoreCase(eventName) //
                || "MEDIA_BUG_START".equalsIgnoreCase(eventName) //
                || "CALL_UPDATE".equalsIgnoreCase(eventName) //
                || "DTMF".equalsIgnoreCase(eventName) //
                || "RELOADXML".equalsIgnoreCase(eventName) //
                || "PLAYBACK_START".equalsIgnoreCase(eventName) //
                || "PLAYBACK_STOP".equalsIgnoreCase(eventName) //
                || "RECV_INFO".equalsIgnoreCase(eventName) //
                || "RE_SCHEDULE".equalsIgnoreCase(eventName)) {
            return null;
        }

        log.error("#271 eventName=" + eventName + ", event=" + event);
        return null;
    }

    /**
     * 似乎没有进来
     */
    @Override
    public void backgroundJobResultReceived(EslEvent event) {
        String eventName = event.getEventName();
        if ("BACKGROUND_JOB".equalsIgnoreCase(eventName)) {
            return;
        }

        log.info("#115 backgroundJobResultReceived, event=" + event);

        FreeswitchPhoneEvent budoEvent = new FreeswitchPhoneEvent();
        budoEvent.setEventName(event.getEventName());
        this.getBudoPhoneEventListener().onEvent(budoEvent);
    }

    public void handleDirectoryLua(HttpServletRequest request, HttpServletResponse response) {
        log.info("#73 handleDirectoryLua, request=" + request + ", parameterMap=" + JSON.toJSONString(Mvcs.getRequestParameterMap(request)) + ", response=" + response);

//        String domain = request.getParameter("domain");
//        String user = request.getParameter("user");
//
        // 密码来自数据库
//        FreeswitchUser freeswitchUser = freeswitchUserService.findByUsername(user);
//        if (null == freeswitchUser) {
//            String xml = this.userXml_NULL(); // 返回随机密码
//            ResponseUtil.writeToResponse(response, xml);
//            return;
//        }
//
//        String xml = this.userXml_2(domain, user, freeswitchUser.getPassword());
//        ResponseUtil.writeToResponse(response, xml);
    }

//  private void handleDialplan(HttpServletRequest request, HttpServletResponse response) {
//      log.info("#44 handleDialplan, request=" + request + ", parameterMap=" + JSON.toJSONString(Mvcs.getRequestParameterMap(request)) + ", response=" + response);
//
//      String gateway = "gw132";
//      String destination_number = request.getParameter("Caller-Destination-Number");
//
//      String params = "{absolute_codec_string=PCMA,origination_caller_id_number=1002}";
//      String diaplan = params + "sofia/gateway/" + gateway + "/" + destination_number;
//
//      String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\r\n" + //
//              "<document type=\"freeswitch/xml\">\r\n" + //
//              "  <section name=\"dialplan\" description=\"RE Dial Plan For FreeSwitch\">\r\n" + //
//              "    <context name=\"default\">\r\n" + //
//              "      <extension name=\"gw132\">\r\n" + //
//              "        <condition field=\"destination_number\" expression=\"^.*$\">\r\n" + //
//              "            <action application=\"set\" data=\"RECORD_TITLE=Recording ${destination_number} ${caller_id_number} ${strftime(%Y-%m-%d %H:%M)}\" />\r\n" + //
//              "            <action application=\"set\" data=\"RECORD_STEREO=true\" />\r\n" + //
//              "            <action application=\"set\" data=\"media_bug_answer_req=true\" />\r\n" + //
//              "            <action application=\"set\" data=\"RECORD_MIN_SEC=1\" />\r\n" + //
//              "            <action application=\"bridge\" data=\"" + diaplan + "\"/>\r\n" + //
//              "        </condition>\r\n" + //
//              "      </extension>\r\n" + //
//              "    </context>\r\n" + //
//              "  </section>\r\n" + //
//              "</document>";
//      ResponseUtil.writeToResponse(response, xml);
//  }
//  private String userXml_NULL() {
//  return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\r\n" + //
//          "    <document type=\"freeswitch/xml\">\r\n" + //
//          "      <section name=\"directory\">\r\n" + //
//          "      </section>\r\n" + //
//          "    </document>";
//}
//private String userXml_2(String domain, String user, String password) {
//  return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\r\n" + //
//          "    <document type=\"freeswitch/xml\">\r\n" + //
//          "      <section name=\"directory\">\r\n" + //
//          "        <domain name=\"" + domain + "\">\r\n" + //
//          "          <params>\r\n" + //
//          "        <param name=\"dial-string\"\r\n" + //
//          "        value=\"{presence_id=${dialed_user}@${dialed_domain}}${sofia_contact(${dialed_user}@${dialed_domain})}\"/>\r\n" + //
//          "          </params>\r\n" + //
//          "          <groups>\r\n" + //
//          "        <group name=\"default\">\r\n" + //
//          "          <users>\r\n" + //
//          "            <user id=\"" + user + "\">\r\n" + //
//          "              <params>\r\n" + //
//          "            <param name=\"password\" value=\"" + password + "\"/>\r\n" + //
//          "            <param name=\"vm-password\" value=\"" + password + "\"/>\r\n" + //
//          "              </params>\r\n" + //
//          "              <variables>\r\n" + //
//          "            <variable name=\"toll_allow\" value=\"domestic,international,local\"/>\r\n" + //
//          "            <variable name=\"accountcode\" value=\"" + user + "\"/>\r\n" + //
//          "            <variable name=\"directory-visible\" value=\"true\"/>\r\n" + //
//          "            <variable name=\"directory-exten-visible\" value=\"true\"/>\r\n" + //
//          "            <variable name=\"limit_max\" value=\"15\"/>\r\n" + //
//          "            <variable name=\"effective_caller_id_name\" value=\"Extension " + user + "\"/>\r\n" + //
//          "            <variable name=\"effective_caller_id_number\" value=\"" + user + "\"/>\r\n" + //
//          "            <variable name=\"outbound_caller_id_name\" value=\"${outbound_caller_name}\"/>\r\n" + //
//          "            <variable name=\"outbound_caller_id_number\" value=\"${outbound_caller_id}\"/>\r\n" + //
//          "            <variable name=\"callgroup\" value=\"techsupport\"/>\r\n" + //
//          "              </variables>\r\n" + //
//          "            </user>\r\n" + //
//          "          </users>\r\n" + //
//          "        </group>\r\n" + //
//          "          </groups>\r\n" + //
//          "        </domain>\r\n" + //
//          "      </section>\r\n" + //
//          "    </document>";
//}
}